
package cn.bytets.rpc.common.bytecode;
import cn.bytets.rpc.common.utils.*;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtNewConstructor;
import javassist.CtNewMethod;
import javassist.LoaderClassPath;
import javassist.NotFoundException;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.security.ProtectionDomain;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;

/**
 * ClassGenerator
 */
public final class ClassGenerator {

    private static final AtomicLong CLASS_NAME_COUNTER = new AtomicLong(0);
    private static final String SIMPLE_NAME_TAG = "<init>";
    private static final Map<ClassLoader, ClassPool> POOL_MAP = new ConcurrentHashMap<ClassLoader, ClassPool>(); //ClassLoader - ClassPool
    private ClassPool mPool;
    private CtClass mCtc;
    private String mClassName;
    private String mSuperClass;
    private Set<String> mInterfaces;
    private List<String> mFields;
    private List<String> mConstructors;
    private List<String> mMethods;
    private Map<String, Method> mCopyMethods; // <method desc,method instance>
    private Map<String, Constructor<?>> mCopyConstructors; // <constructor desc,constructor instance>
    private boolean mDefaultConstructor = false;

    private StringBuffer fieldBuf = new StringBuffer();
    private StringBuffer methodBuf = new StringBuffer();
    private StringBuffer constructorsBuf = new StringBuffer();

    private ClassGenerator() {
    }

    private ClassGenerator(ClassPool pool) {
        mPool = pool;
    }

    public static ClassGenerator newInstance() {
        return new ClassGenerator(getClassPool(Thread.currentThread().getContextClassLoader()));
    }

    public static ClassGenerator newInstance(ClassLoader loader) {
        return new ClassGenerator(getClassPool(loader));
    }

    /**
     * 判断是否静态接口类
     * @param cl
     * @return
     */
    public static boolean isDynamicClass(Class<?> cl) {
        return ClassGenerator.DC.class.isAssignableFrom(cl);
    }

    public static ClassPool getClassPool(ClassLoader loader) {
        if (loader == null) {
            return ClassPool.getDefault();
        }

        ClassPool pool = POOL_MAP.get(loader);
        if (pool == null) {
            pool = new ClassPool(true);
            pool.appendClassPath(new LoaderClassPath(loader));
            POOL_MAP.put(loader, pool);
        }
        return pool;
    }

    public void addMethod(Method m,String body){
        addMethod(m.getModifiers(),m.getName(),m.getParameterTypes(),m.getReturnType(),m.getExceptionTypes(),body);
    }
    public void addMethod(int modifiers,String methodName,
                          Class<?>[] parameterTypes,Class<?> returnType,Class<?>[] exceptionTypes,String body){
        if (null==mMethods){
            mMethods = new ArrayList<String>();
        }
        ClassGeneratorUtils.getModifiers(modifiers,methodBuf);
        methodBuf.append(" ")
                 .append(returnType.getName())
                 .append(" ").append(methodName)
                 .append("(");
        montageParam(parameterTypes,methodBuf);
        methodBuf.append(")");
        montageExcepiton(exceptionTypes,methodBuf);
        methodBuf.append("{")
                 .append(body)
                 .append("}");
        mMethods.add(methodBuf.toString());
        methodBuf.setLength(0);
    }
    public void addField(Field field){
        addField(field.getModifiers(),field.getType().getName(),field.getName());
    }

    public void addField(int modifiers,String fieldType,String fieldName){
        if (null==mFields){
            mFields = new ArrayList<>();
        }
        ClassGeneratorUtils.getModifiers(modifiers,fieldBuf);
        fieldBuf.append(" ")
                .append(fieldType)
                .append(" ")
                .append(fieldName)
                .append(";");
        mFields.add(fieldBuf.toString());
        fieldBuf.setLength(0);
    }
    public void addInterface(String interfaceName){
        if (null==mInterfaces){
            mInterfaces = new LinkedHashSet<>();
        }
        mInterfaces.add(interfaceName);
    }
    public void addSuperclass(String superClass){
        this.mSuperClass = superClass;
    }
    public void addConstructor(int modifiers,Class<?>[] parameterTypes,String body){
        if(null==mConstructors){
            mConstructors = new ArrayList<>();
        }
        ClassGeneratorUtils.getModifiers(modifiers,constructorsBuf);
        constructorsBuf.append(" "+SIMPLE_NAME_TAG).append("(");
        montageParam(parameterTypes,constructorsBuf);
        constructorsBuf.append("){")
                        .append(body).append("}");
        mConstructors.add(constructorsBuf.toString());
        constructorsBuf.setLength(0);
    }
    private void montageParam(Class<?>[] parameterTypes,StringBuffer buffer){
        if(null!=parameterTypes&&parameterTypes.length>0){
            for (int i=0;i<parameterTypes.length;i++){
                if(i>0){
                    buffer.append(",");
                }
                buffer.append(parameterTypes[i].getName())
                        .append(" ")
                        .append("val").append(i);
            }
        }
    }
    private void montageExcepiton(Class<?>[] exceptionTypes,StringBuffer buffer){
        if(null!=exceptionTypes&&exceptionTypes.length>0){
            buffer.append("throws").append(" ");
            for (int i=0;i<exceptionTypes.length;i++){
                if(i>0){
                    buffer.append(",");
                }
                buffer.append(exceptionTypes[i].getName());
            }
        }
    }
    public void addDefaultConstructor(){
        this.mDefaultConstructor = true;
    }
    public void addClassName(String className){
        this.mClassName = className;
    }
    public Class<?> toClass() {
        return toClass(ClassUtils.getClassLoader(ClassGenerator.class),
                getClass().getProtectionDomain());
    }

    /**
     * 生成代理类
     * @param loader
     * @param pd
     * @return
     */
    public Class<?> toClass(ClassLoader loader, ProtectionDomain pd) {
        if (mCtc != null) {
            mCtc.detach();
        }
        long id = CLASS_NAME_COUNTER.getAndIncrement();
        try{
            CtClass superclass = mSuperClass == null ? null : mPool.get(mSuperClass);
            if (mClassName == null) {
                mClassName = (mSuperClass == null || javassist.Modifier.isPublic(superclass.getModifiers())
                        ? ClassGenerator.class.getName() : mSuperClass + "$sc") + id;
            }
            mCtc = mPool.makeClass(mClassName);
            if(null!=mSuperClass){
                mCtc.setSuperclass(superclass);
            }
            if(null!=mFields){
                for (String field:mFields){
                    mCtc.addField(CtField.make(field,mCtc));
                }
            }
            if(null!=mMethods){
                for (String method:mMethods){
                    mCtc.addMethod(CtNewMethod.make(method,mCtc));
                }
            }
            if(mDefaultConstructor){
                mCtc.addConstructor(CtNewConstructor.defaultConstructor(mCtc));
            }
            /**
             * 将代理类打上静态标志
             */
            mCtc.addInterface(mPool.get(DC.class.getName()));
            if(null!=mInterfaces){
                for (String faces:mInterfaces){
                    mCtc.addInterface(mPool.makeClass(faces));
                }
            }
            if(null!=mConstructors){
                for (String name:mConstructors){
                    mCtc.addConstructor(CtNewConstructor.make(name.replace(SIMPLE_NAME_TAG,mCtc.getSimpleName()),mCtc));
                }
            }
            return mCtc.toClass(loader, pd);
        } catch (RuntimeException e) {
            throw e;
        } catch (NotFoundException e) {
            throw new RuntimeException(e.getMessage(), e);
        } catch (CannotCompileException e) {
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    public void release() {
        if (mCtc != null) {
            mCtc.detach();
        }
        if (mInterfaces != null) {
            mInterfaces.clear();
            mInterfaces = null;
        }
        if (mFields != null) {
            mFields.clear();
            mFields = null;
        }
        if (mMethods != null) {
            mMethods.clear();
            mMethods = null;
        }
        if (mConstructors != null) {
            mConstructors.clear();
            mConstructors = null;
        }
        if (mCopyMethods != null) {
            mCopyMethods.clear();
            mCopyMethods = null;
        }
        if (mCopyConstructors != null) {
            mCopyConstructors.clear();
            mCopyConstructors = null;
        }
        fieldBuf.setLength(0);
        fieldBuf = null;
        methodBuf.setLength(0);
        methodBuf = null;
        constructorsBuf.setLength(0);
        constructorsBuf = null;
    }
    public static interface DC {

    } // dynamic class tag interface.
}